package se19team1e.controller;

import java.util.HashMap;
import java.util.Map;

import se19team1e.dao.abstrakt.jaas.AdvertiserDAO;
import se19team1e.dao.factory.DAOFactory;
import se19team1e.dao.factory.DAOFactorySelector;
import se19team1e.entity.AccountDTO;
import se19team1e.entity.AdvertiserDTO;
import se19team1e.entity.JobAdvertDTO;
import se19team1e.exception.DataAccessException;
import se19team1e.gui.BaseScreen;
import se19team1e.gui.CreateJobAdvertForm;
import se19team1e.gui.DeleteJobAdvertForm;
import se19team1e.gui.JobAdvertResponseDetailScreen;
import se19team1e.gui.JobAdvertResponseResultScreen;
import se19team1e.gui.JobAdvertSearchForm;
import se19team1e.gui.LoginForm;
import se19team1e.gui.MessageBoxFactory;
import se19team1e.gui.UpdateJobAdvertForm;
import se19team1e.gui.ViewJobAdvertForm;
import se19team1e.gui.WithdrawJobAdvertScreen;
import se19team1e.gui.awt.AWTCreateJobAdvertForm;
import se19team1e.gui.awt.AWTDeleteJobAdvertForm;
import se19team1e.gui.awt.AWTJobAdvertResponseDetailScreen;
import se19team1e.gui.awt.AWTJobAdvertResponseResultsScreen;
import se19team1e.gui.awt.AWTJobAdvertSearchForm;
import se19team1e.gui.awt.AWTLoginForm;
import se19team1e.gui.awt.AWTUpdateJobAdvertForm;
import se19team1e.gui.awt.AWTViewJobAdvertForm;
import se19team1e.gui.awt.AWTWithdrawJobAdvertScreen;
import se19team1e.gui.awt.MessageBoxAWTFactory;
import se19team1e.search.SearchEngine;
import se19team1e.search.SearchEngineFactory;

/**
 * The main application object.
 * 
 * <p>
 * All the screen objects are created and cached in the application, 
 * and the entry point to the application is found here as well.
 * </p>
 * <p>
 * The application object also help to enforce a single visible screen at any given time,
 * as all screen display goes through the setCurrentScreen call.
 * </p>
 * 
 * @author lai shiaw san
 *
 */
public class Application {
    
    /**
     * This helps to keep track of the current screen.
     */
    private BaseScreen currentScreen;
    
    /**
     * This helps to cache all the screens in the application created at startup.
     */
    private final Map<Class<? extends BaseScreen>, BaseScreen> screens = new HashMap<Class<? extends BaseScreen>, BaseScreen>();

    /**
     * This is a reference to the message box factory to use when creating new message boxes for display.
     */
    private MessageBoxFactory messageBoxFactory;

    /**
     * This is a reference to the search engine to use for searching.
     */
    private SearchEngine< JobAdvertDTO, String > searchEngine;
    
    /**
     * This helps to keep track of the current logged in account, if any.
     */
    private AccountDTO currentAccount;
    
    /**
     * Get the current logged in account, if any.
     * @return Current logged in account, if any.
     */
    public AccountDTO getCurrentAccount() {
        return this.currentAccount;
    }
    
    /**
     * Set the current logged in account.
     * 
     * <p>
     * If there are any existing logged in account, it is simply replaced.
     * </p>
     * @param currentAccount New logged in account.
     */
    public void setCurrentAccount( AccountDTO currentAccount ) {
        this.currentAccount = currentAccount;
    }
    
    /**
     * Sets the specified screen as the current active screen.
     * 
     * <p>
     * Will trigger an enables/disable of the button/menu on the screen as well, before showing the screen.
     * </p>
     * @param currentScreen New screen to display as active screen.
     */
    public void setCurrentScreen( BaseScreen currentScreen ) {
        // enable/disable button/menu
        // allow for null, due to testing purpose
        if ( currentScreen != null ) {
            currentScreen.updateAvailableActions();
        }
        
        // hide previous screen if different!
        if ( this.currentScreen != null && this.currentScreen != currentScreen ) {
            this.currentScreen.hide();
        }

        // show new screen
        this.currentScreen = currentScreen;
        // allow for null, due to testing purpose
        if ( this.currentScreen != null ) {
            this.currentScreen.show();
        }
    }

    /**
     * Gets the current active screen.
     * 
     * @return Current active screen.
     */
    public BaseScreen getCurrentScreen() {
        return this.currentScreen;
    }
    
    /**
     * Gets the message box factory to use to create message boxes.
     * @return The message box factory to use to create message boxes.
     */
    public MessageBoxFactory getMessageBoxFactory() {
        return this.messageBoxFactory;
    }
    
    /**
     * Sets the message box factory to use to create message boxes.
     * @param messageBoxFactory The new message box factory to use to create message boxes.
     */
    public void setMessageBoxFactory( MessageBoxFactory messageBoxFactory ) {
        this.messageBoxFactory = messageBoxFactory;
    }

    /**
     * Gets the Search Engine to use for searching.
     * @return The Search Engine to use for searching.
     */
    public SearchEngine< JobAdvertDTO, String > getSearchEngine() {
        return this.searchEngine;
    }
    
    /**
     * Sets the Search Engine to use for searching.
     * @param searchEngine The new Search Engine to use for searching.
     */
    public void setSearchEngine( SearchEngine< JobAdvertDTO, String > searchEngine) {
        this.searchEngine= searchEngine;
    }

    /**
     * Initializes the application.
     * 
     * <p>
     * Data is loaded from persistence storage, screens are created and cached, and the initial controller and screen is set.
     * </p>
     */
    public void initialize() {
        this.setMessageBoxFactory( new MessageBoxAWTFactory() );
        
        this.setSearchEngine(SearchEngineFactory.getSearchEngine("Properties"));
        
        this.createScreens();
        // To display the first screen that user sees
        this.selectLogin();
        
        //this.selectSearchJobAdvert();
    }
    
    /**
     * Shuts down the application.
     * 
     * <p>
     * The current logged in account, if any, is logged out, the cached screens are disposed, and the data are flushed to persitence storage.
     * </p>
     */
    public void shutdown() {
        // log out any current logged in account
        this.clearCurrentAccount();
        
        // destroy all screens
        this.destroyScreens();
    }
    
    /**
     * Get the cached screen specified by the class type.
     * 
     * @param clazz Screen type to retrieve.
     * @return Cached screen instance.
     */
    @SuppressWarnings("unchecked")
    public <T> T getScreen( Class<T> clazz ) {
        return (T) this.screens.get(clazz);
    }

    /**
     * Logs out any existing logged in account, and clears the reference.
     */
    public void logout() {
        // clear login account if any
        this.clearCurrentAccount();
        
        // switch to login form instead 
        this.selectLogin();
        
        // go to search form
        //this.selectSearchJobAdvert();
    }
    
    /**
     * Initializes and displays the default create job advert screen.
     * @throws DataAccessException 
     */
    public void selectCreateJobAdvert() throws DataAccessException {
        // create and delegate display to create job advert controller
        CreateJobAdvertController controller = new CreateJobAdvertController( this );
        controller.initialize();
    }

    /**
     * Initializes and displays the default search job advert screen.
     */
    public void selectSearchJobAdvert() {
        // create and delegate display to search job advert controller
        JobAdvertSearchController controller = new JobAdvertSearchController( this );
        controller.initialize();
    }
    
    /**
     * Initializes and displays the default login screen.
     */
    public void selectLogin() {
        // create and delegate display to login controller
        LoginController controller = new LoginController( this );
        controller.initialize();
    }

    /**
     * This is a helper method, to lookup the advertiser base on the current logged in account.
     * @return Advertiser entity, if there is a current logged in account, and he is an advertiser.
     */
    public AdvertiserDTO getCurrentAdvertiser() {
        // if not logged in, no advertiser to return
        if ( this.getCurrentAccount() == null ) {
            return null;
        }
        
        // get dao
        DAOFactory daoFactory = DAOFactorySelector.getDAOFactory();
        AdvertiserDAO advertiserDAO = daoFactory.getAdvertiserDAO();
        
        String currentAccountId = this.getCurrentAccount().getId();
        AdvertiserDTO advertiser=null;
		try {
			advertiser = advertiserDAO.retrieveById(currentAccountId);
			// Chen Lim - modified on 21st October 2011. If not, this method 
			// will always return a advertiser object without advertiserID 
			// which causes problem with the caller if null is not returned for such cases.
			if (advertiser.getId() == null) {
				return null;
			}
		} catch (DataAccessException e) {
			e.printStackTrace();
		}
        
        return advertiser;
    }

    /**
     * Caches the screen for lookup later.
     * 
     * @param screenClass Interface class of screen to cache.
     * @param screen Actual screen being cached.
     */
    public void cacheScreen( Class< ? extends BaseScreen > screenClass, BaseScreen screen ) {
        this.screens.put( screenClass, screen );
    }

    /**
     * Clears the current logged in account.
     */
    private void clearCurrentAccount() {
        if ( this.currentAccount != null ) {
            this.currentAccount.logout();
        }
        this.currentAccount = null;
    }
    
    /** 
     * Creates and caches all screens required by the application.
     */
    private void createScreens() {
        this.cacheScreen( CreateJobAdvertForm.class, new AWTCreateJobAdvertForm( this ) );
        this.cacheScreen( LoginForm.class, new AWTLoginForm( this ) );
        this.cacheScreen( JobAdvertSearchForm.class, new AWTJobAdvertSearchForm( this ) );
        this.cacheScreen( ViewJobAdvertForm.class, new AWTViewJobAdvertForm( this ) );
        this.cacheScreen( UpdateJobAdvertForm.class, new AWTUpdateJobAdvertForm( this ) );
        this.cacheScreen( DeleteJobAdvertForm.class, new AWTDeleteJobAdvertForm( this ) );
        this.cacheScreen( WithdrawJobAdvertScreen.class, new AWTWithdrawJobAdvertScreen( this ) );
        this.cacheScreen(JobAdvertResponseResultScreen.class, new AWTJobAdvertResponseResultsScreen(this));
        this.cacheScreen(JobAdvertResponseDetailScreen.class, new AWTJobAdvertResponseDetailScreen(this,null));
    }
    
    /**
     * Hides and destroys all cached screen instances.
     */
    private void destroyScreens() {
        // for each screen
        for ( BaseScreen screen : this.screens.values() ) {
            // hide and dispose
            screen.hide();
            screen.dispose();
        }
    }

    /**
     * The application entry point.
     * 
     * @param args Arguments passed to the application.
     */
    public static void main( String[] args ) {
        // create the application
        Application application = new Application();
        
        // initialize the application
        application.initialize();
    }

}